package com.chen.recruit.service.impl;

import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chen.recruit.common.api.CommonResult;
import com.chen.recruit.common.constants.MessageConstants;
import com.chen.recruit.common.constants.SysConstants;
import com.chen.recruit.common.enums.CollegeType;
import com.chen.recruit.common.enums.CommonStatus;
import com.chen.recruit.common.enums.UserType;
import com.chen.recruit.common.utils.ALiYunOssUtil;
import com.chen.recruit.common.utils.ExcelUtils;
import com.chen.recruit.common.utils.SysUtils;
import com.chen.recruit.domain.*;
import com.chen.recruit.domain.dto.UserDto;
import com.chen.recruit.domain.vo.*;
import com.chen.recruit.mapper.*;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.chen.recruit.service.UserService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

import static com.chen.recruit.common.enums.UserType.*;

/**
 * 用户Service业务层处理
 *
 * @author cgy
 * @date 2022-08-20
 */
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Value("${sa-token.token-prefix}")
    private String tokenPrefix;
    private final RoleMapper roleMapper;
    private final PostMapper postMapper;
    private final ResumeMapper resumeMapper;
    private final CollectMapper collectMapper;
    private final CollegeMapper collegeMapper;
    private final UserRoleMapper userRoleMapper;
    private final ResumeDeliverMapper resumeDeliverMapper;
    private final RedisTemplate<String, String> redisTemplate;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult addOrUpdateUser(User user, UserType type) {
        boolean exists = checkUsernameUnique(user.getId(), user.getUsername());
        if (exists) {
            return CommonResult.fail(MessageConstants.USERNAME_REPEAT);
        }
        exists = checkEmailUnique(user.getId(), user.getEmail());
        if (exists) {
            return CommonResult.fail(MessageConstants.EMAIL_REPEAT);
        }

        processUserInfo(user, type);

        if (ObjectUtil.isNotNull(user.getPostId()) && user.getPostId() == -1) {
            return CommonResult.fail(MessageConstants.ADMIN_POST_ERROR);
        }
        if (ObjectUtil.isNotNull(user.getCollegeId())) {
            if (user.getCollegeId() == -1) {
                return CommonResult.fail(MessageConstants.ADMIN_COLLEGE_SELECT_FAIL);
            }
            if (user.getCollegeId() == -2) {
                return CommonResult.fail(MessageConstants.STUDENT_COLLEGE_SELECT_FAIL);
            }
        } else {
            user.setCollegeId(null);
        }

        if (ObjectUtil.isNull(user.getId())) {
            user.setPassword(SaSecureUtil.md5(user.getPassword()));
            this.save(user);
        } else {
            this.updateById(user);
        }

        List<Integer> roleIds = user.getRoleIds();
        if (CollUtil.isNotEmpty(roleIds)) {
            userRoleMapper.delete(Wrappers.<UserRole>lambdaQuery()
                    .eq(UserRole::getUserId, user.getId()));
            for (Integer roleId : roleIds) {
                userRoleMapper.insert(new UserRole(user.getId(), roleId));
            }
        }

        return this.saveOrUpdate(user) ? CommonResult.success() : CommonResult.fail();
    }

    //TODO 业务逻辑待完善
    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult deleteUserByIds(Integer[] ids) {
        baseMapper.deleteBatchIds(Arrays.asList(ids));
        userRoleMapper.delete(Wrappers.<UserRole>lambdaQuery().in(UserRole::getUserId, ids));
        collectMapper.delete(Wrappers.<Collect>lambdaQuery().in(Collect::getStudentId, ids));

        List<Integer> resumeIds = resumeMapper.selectList(Wrappers.<Resume>lambdaQuery()
                .in(Resume::getStudentId, ids)).stream().map(Resume::getId).collect(Collectors.toList());
        if (CollUtil.isNotEmpty(resumeIds)) {
            resumeMapper.deleteBatchIds(resumeIds);
            resumeDeliverMapper.delete(Wrappers.<ResumeDeliver>lambdaQuery().in(ResumeDeliver::getResumeId, resumeIds));
        }
        return CommonResult.success();
    }

    @Override
    public Object getUserInfoById(Integer id, UserType type) {
        List<Integer> roleIds = userRoleMapper.selectList(Wrappers.<UserRole>lambdaQuery()
                .eq(UserRole::getUserId, id)).stream().map(UserRole::getRoleId).collect(Collectors.toList());
        switch (type) {
            case ADMIN:
                Admin admin = BeanUtil.toBean(baseMapper.selectById(id), Admin.class);
                admin.setRoleIds(roleIds);
                return admin;
            case ENTERPRISE:
                Enterprise enterprise = BeanUtil.toBean(baseMapper.selectById(id), Enterprise.class);
                enterprise.setRoleIds(roleIds);
                return enterprise;
            case STUDENT:
                Student student = BeanUtil.toBean(baseMapper.selectById(id), Student.class);
                student.setRoleIds(roleIds);
                return student;
            default:
                return null;
        }
    }

    @Override
    public Page<Admin> getAdminList(Admin admin, Page<Admin> page) {
        return baseMapper.pageQueryAdminListByCondition(admin, page);
    }

    @Override
    public Page<Enterprise> getEnterpriseList(Enterprise enterprise, Page<Enterprise> page) {
        return baseMapper.pageQueryEnterpriseListByCondition(enterprise, page);
    }

    @Override
    public Page<Student> getStudentList(Student student, Page<Student> page) {
        return baseMapper.pageQueryStudentListByCondition(student, page);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult importUserList(MultipartFile file, UserType type) {
        if (file.isEmpty()) {
            return CommonResult.fail(MessageConstants.FILE_IS_EMPTY);
        }

        List<User> users = new ArrayList<>();
        switch (type) {
            case ADMIN:
                users = BeanUtil.copyToList(ExcelUtils.importExcel(file, Admin.class), User.class);
                break;
            case ENTERPRISE:
                users = BeanUtil.copyToList(ExcelUtils.importExcel(file, Enterprise.class), User.class);
                break;
            case STUDENT:
                users = BeanUtil.copyToList(ExcelUtils.importExcel(file, Student.class), User.class);
                break;
        }

        for (User user : users) {
            boolean exists = checkUsernameUnique(user.getId(), user.getUsername());
            if (exists) {
                return CommonResult.fail(MessageConstants.USERNAME_REPEAT);
            }
            exists = checkEmailUnique(user.getId(), user.getEmail());
            if (exists) {
                return CommonResult.fail(MessageConstants.EMAIL_REPEAT);
            }
            if (StringUtils.isBlank(user.getPassword())) {
                return CommonResult.fail(MessageConstants.PWD_NOT_BE_EMPTY);
            }

            processUserInfo(user, type);

            if (user.getPostId() == -1) {
                return CommonResult.fail(MessageConstants.ADMIN_POST_ERROR);
            }
            if (ObjectUtil.isNotNull(user.getCollegeId())) {
                if (user.getCollegeId() == -1) {
                    return CommonResult.fail(MessageConstants.ADMIN_COLLEGE_SELECT_FAIL);
                }
                if (user.getCollegeId() == -2) {
                    return CommonResult.fail(MessageConstants.STUDENT_COLLEGE_SELECT_FAIL);
                }
            } else {
                user.setCollegeId(null);
            }

            user.setPassword(SaSecureUtil.md5(user.getPassword()));
            this.save(user);

            Role role = roleMapper.selectOne(Wrappers.<Role>lambdaQuery()
                    .eq(Role::getName, user.getRoleName()));
            if (ObjectUtil.isNotNull(role)) {
                userRoleMapper.insert(new UserRole(user.getId(), role.getId()));
            }
        }

        return CommonResult.success();
    }


    @Override
    public List<Admin> exportAdminList(Admin admin) {
        return baseMapper.selectAdminListByCondition(admin);
    }

    @Override
    public List<Enterprise> exportEnterpriseList(Enterprise enterprise) {
        return baseMapper.selectEnterpriseListByCondition(enterprise);
    }

    @Override
    public List<Student> exportStudentList(Student student) {
        return baseMapper.selectStudentListByCondition(student);
    }

    @Override
    public CommonResult userLogin(LoginVo vo) {
        String captcha = redisTemplate.opsForValue().get(SysConstants.CAPTCHA);
        if (StringUtils.isBlank(vo.getCode()) || !vo.getCode().equalsIgnoreCase(captcha)) {
            return CommonResult.fail(MessageConstants.CAPTCHA_ERROR);
        }

        User user = this.getOne(Wrappers.<User>lambdaQuery()
                .eq(User::getUsername, vo.getUsername())
                .eq(User::getPassword, SaSecureUtil.md5(vo.getPassword())));
        if (ObjectUtil.isNull(user)) {
            return CommonResult.fail(MessageConstants.USER_LOGIN_FAIL);
        }
        if (user.getStatus() == CommonStatus.DISABLE) {
            return CommonResult.fail(MessageConstants.USERNAME_IS_DISABLE);
        }

        Date date = new Date();
        UserDto userDto = BeanUtil.toBean(user, UserDto.class);
        userDto.setLastLoginTime(ObjectUtil.isNull(user.getLoginTime()) ? date : user.getLoginTime());
        userDto.setLoginTime(date);
        user.setLoginTime(date);

        StpUtil.login(user.getId(), vo.getRememberMe());
        StpUtil.getTokenSession().set(SysConstants.LOGIN_USER, userDto);

        String token = tokenPrefix + StrUtil.SPACE + StpUtil.getTokenValue();

        return this.updateById(user) ? CommonResult.success(token) : CommonResult.fail();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult resetPwd(ResetPwdVo vo) {
        String captcha = redisTemplate.opsForValue().get(SysConstants.EMAIL_CAPTCHA);
        if (StringUtils.isBlank(vo.getCode()) || !vo.getCode().equalsIgnoreCase(captcha)) {
            return CommonResult.fail(MessageConstants.CAPTCHA_ERROR);
        }

        boolean flag = this.update(Wrappers.<User>lambdaUpdate()
                .eq(User::getEmail, vo.getEmail())
                .eq(User::getUsername, vo.getUsername())
                .set(User::getPassword, SaSecureUtil.md5(vo.getNewPwd())));

        return flag ? CommonResult.success() : CommonResult.fail(MessageConstants.RESET_PWD_FAIL);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult updatePwd(UpdatePwdVo vo) {
        UserDto userDto = SysUtils.getLoginUser();
        if (!SaSecureUtil.md5(vo.getOldPwd()).equals(userDto.getPassword())) {
            return CommonResult.fail(MessageConstants.OLD_PWD_ERROR);
        }
        User user = BeanUtil.toBean(userDto, User.class);
        user.setPassword(SaSecureUtil.md5(vo.getNewPwd()));
        return this.updateById(user) ? CommonResult.success() : CommonResult.fail();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult updateUserAvatar(MultipartFile file) {
        if (file.isEmpty()) {
            return CommonResult.fail(MessageConstants.FILE_IS_EMPTY);
        }
        String imgUrl = ALiYunOssUtil.uploadFileAvatar(file);
        if (StringUtils.isBlank(imgUrl)) {
            return CommonResult.fail();
        }
        UserDto userDto = SysUtils.getLoginUser();
        User user = baseMapper.selectById(userDto.getId());
        user.setAvatar(imgUrl);
        userDto.setAvatar(imgUrl);
        StpUtil.getTokenSession().set(SysConstants.LOGIN_USER, userDto);
        return this.updateById(user) ? CommonResult.success(imgUrl) : CommonResult.fail();
    }

    private boolean checkUsernameUnique(Integer id, String username) {
        return baseMapper.exists(Wrappers.<User>lambdaQuery()
                .eq(User::getUsername, username)
                .ne(ObjectUtil.isNotNull(id), User::getId, id));
    }

    private boolean checkEmailUnique(Integer id, String email) {
        return baseMapper.exists(Wrappers.<User>lambdaQuery()
                .eq(User::getEmail, email)
                .ne(ObjectUtil.isNotNull(id), User::getId, id));
    }

    private void processUserInfo(User user, UserType type) {
        Integer collegeId;
        switch (type) {
            case ADMIN:
                user.setType(ADMIN);
                user.setPostId(checkPostLegality(user.getPostId(), user.getPostName()));
                collegeId = checkCollegeLegality(user.getCollegeId(), user.getCollegeName(), ADMIN);
                user.setCollegeId(collegeId);
                break;
            case ENTERPRISE:
                user.setType(ENTERPRISE);
                CommonStatus status = user.getStatus();
                user.setStatus(ObjectUtil.isNull(status) ? CommonStatus.DISABLE : status);
                break;
            case STUDENT:
                user.setType(STUDENT);
                collegeId = checkCollegeLegality(user.getCollegeId(), user.getCollegeName(), STUDENT);
                user.setCollegeId(collegeId);
                break;
        }
    }

    private Integer checkCollegeLegality(Integer collegeId, String collegeName, UserType type) {
        College college = collegeMapper.selectOne(Wrappers.<College>lambdaQuery()
                .eq(ObjectUtil.isNotNull(collegeId), College::getId, collegeId).or()
                .eq(StringUtils.isNotBlank(collegeName), College::getName, collegeName));

        if (type == ADMIN) {
            if (ObjectUtil.isNull(college) || college.getType() == CollegeType.CLASS
                    || college.getStatus() == CommonStatus.DISABLE) {
                return -1;
            }
        }
        if (type == STUDENT) {
            if (ObjectUtil.isNull(college) || college.getType() != CollegeType.CLASS
                    || college.getStatus() == CommonStatus.DISABLE) {
                return -2;
            }
        }

        return college.getId();
    }

    private Integer checkPostLegality(Integer postId, String postName) {
        Post post = postMapper.selectOne(Wrappers.<Post>lambdaQuery()
                .eq(ObjectUtil.isNotNull(postId), Post::getId, postId).or()
                .eq(StringUtils.isNotBlank(postName), Post::getName, postName));
        return ObjectUtil.isNull(post) || post.getStatus() == CommonStatus.DISABLE ? -1 : post.getId();
    }

}
